;==================================
;头部协议区
;==================================
SECTION header vstart=0
    program_length dd program_end
    ; 程序入口
    code_entry dw start
               dd section.code1.start
    realloc_tbl_len dw (header_end-code_1_segment)/4
    ; 程序段重定位表
    code_1_segment dd section.code1.start
    code_2_segment dd section.code2.start
    data_1_segment dd section.data1.start
    data_2_segment dd section.data2.start
    stack_segment dd section.stack.start
    header_end:
;===================================
SECTION code1 align=16 vstart=0
; put_string 入参 bx=字符串首地址
put_string:
    mov cl,[bx]
    or cl,cl ; 如果为0则退出循环
    jz .exit
    call put_char
    inc bx
    jmp put_string
    .exit: ret
; ===============
; put_char cl=将要输出的字符
; ===============
put_char:
    push ax
    push bx
    push cx
    push dx
    push ds
    push es
    ; 因为要控制光标移动，所以先取当前光标位置
    mov dx,0x3d4 ; 显卡的索引寄存器端口号为 0x3d4
    ; 索引值是有一个列表的，每个索引值都对应着一个显卡的内部寄存器
    ; 先通过索引值告诉显卡要操作的寄存器
    mov al,0x0e
    out dx,al
    ; 再通过数据端口进行读写
    mov dx,0x3d5
    in al,dx
    mov ah,al

    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    in al,dx

    mov bx,ax ; 用 BX 来记录光标位置

    ; 开始输出文本
    cmp cl,0x0d  ; 回车符？
    jnz put_0a  ; 不是则进入下一个判断，看看是不是换行符
    mov ax,bx   ; 当前游标除以80，商即为行数（从0开始），再乘以80，即为当前行首游标
    mov bl,80
    div bl     ; AL--商，代表行数，AH--余数，舍弃
    mul bl     ; AX中为乘积
    mov bx,ax  ; 结果保存到 BX 中
    jmp set_cursor

put_0a:
    cmp cl,0x0a  ; 换行符？
    jnz put_normal
    add bx,80
    jmp scroll_screen

put_normal:
    mov ax,0xb800
    mov es,ax
    shl bx,1 ; 左移一位就是乘以2。BX是坐标位置，可代表是第多少个字符，乘2后就是要在显卡中写入的位置
    mov ch,0x07
    mov [es:bx],cx

    shr bx,1 ; 刚刚 bx 左移了一位，现在右移回来
    inc bx

scroll_screen:
    cmp bx,2000  ; 80x25 模式下最多就显示2000个字符。光标还要占一个字符，文字最多1999个
    jl set_cursor ; jmp if less than
    ; 滚屏本质是将2~25行全部向上挪一行，最后一行用黑底白字的空格填充
    mov ax,0xb800
    mov ds,ax
    mov es,ax
    mov si,2*80
    mov di,0
    mov cx,80*24
    cld
    rep movsw

    mov bx,1920*2 ; 定位到最后一行
    mov cx,80 ; 循环80次
cls:
    mov word[es:bx],0x0720
    add bx,2
    loop cls

    mov bx,1920

set_cursor:
    mov dx,0x3d4
    mov al,0x0e
    out dx,al
    mov dx,0x3d5
    mov al,bh
    out dx,al

    mov dx,0x3d4
    mov al,0x0f
    out dx,al
    mov dx,0x3d5
    mov al,bl
    out dx,al
    
    pop es
    pop ds
    pop dx
    pop cx
    pop bx
    pop ax

    ret

start:
    ; 先重设段地址，当前段地址还在主程序设置的 DS=ES=0x1000,SS-->0x0000
    ; 要设置成用户程序自己的地址 DS-->data_1_segment，ES-->?,SS-->stack_segment
    mov ax,[stack_segment]
    mov ss,ax
    mov sp,stack_end

    mov ax,[data_1_segment]
    mov ds,ax

    mov bx,msg0
    call put_string

    push word [es:code_2_segment]
    mov ax,begin
    push ax

    retf

continue:
    mov ax,[es:data_2_segment]
    mov ds,ax

    mov bx,msg1
    call put_string
    jmp $

SECTION code2 align=16 vstart=0

begin:
    push word [es:code_1_segment]
    mov ax,continue
    push ax

    retf

SECTION data1 align=16 vstart=0
    msg0 db '  This is NASM - the famous Netwide Assembler. '
         db 'Back at SourceForge and in intensive development! '
         db 'Get the current versions from http://www.nasm.us/.'
         db 0x0d,0x0a,0x0d,0x0a
         db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
         db '     xor dx,dx',0x0d,0x0a
         db '     xor ax,ax',0x0d,0x0a
         db '     xor cx,cx',0x0d,0x0a
         db '  @@:',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     add ax,cx',0x0d,0x0a
         db '     adc dx,0',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     cmp cx,1000',0x0d,0x0a
         db '     jle @@',0x0d,0x0a
         db '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
         db 0
SECTION data2 align=16 vstart=0
    msg1 db '  The above contents is written by LeeChung. '
         db '2011-05-06'
         db 0
SECTION stack align=16 vstart=0
    resb 256
stack_end:
;===================================
SECTION trailer align=16
program_end: